package io.gitee.chearnee.fw.common.utils.compare;

import com.google.common.collect.Sets;
import io.gitee.chearnee.fw.common.utils.ClassUtil;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Consumer;

/**
 * 两个对象深度比较值，比较对象值的变化
 * @author cn
 * @version 1.0
 * @description
 * @date 2023/7/21 11:14
 */

@UtilityClass
public class CompareUtil {

    private static final Map<Class<?>, Field[]> COMPARE_FIELD_MAP = new ConcurrentReferenceHashMap<>(256);


    public static <T> void compare(T t1, T t2, String desc, Consumer<CompareResult> resultConsumer) {
        if (t1 == null && t2 == null) {
            return;
        }
//        if(desc==null){
//            return;
//        }
        if (Objects.equals(t1, t2)) {
            return;
        }
//        if (doNullCompare(t1, t2, desc, resultConsumer)) return;
        T temp = Optional.ofNullable(t1).orElse(t2);
        Class<?> tempClass = temp.getClass();
        if (entityCompare(t1, t2, resultConsumer, tempClass)) return;
        if (generalObjectCompare(t1, t2, desc, resultConsumer, tempClass)) return;
        iterableCompare(t1, t2, desc, resultConsumer, tempClass);
    }

    private static <T> boolean generalObjectCompare(T t1, T t2, String desc, Consumer<CompareResult> resultConsumer, Class<?> tempClass) {
        if (!(ClassUtil.isEntity(tempClass) || ClassUtil.isIterable(tempClass))) {
            if (resultConsumer != null) {
                CompareResult build = CompareResult.builder().t1Value(t1).t2Value(t2).desc(desc).build();
                resultConsumer.accept(build);
            }
            return true;
        }
        return false;
    }


    private static <T> void iterableCompare(T t1, T t2, String desc, Consumer<CompareResult> resultConsumer, Class<?> tempClass) {
        if (ClassUtil.isIterable(tempClass)) {
            Set<Object> t11Set = null;
            Set<Object> t22Set = null;
//            if(t1==null||t2==null){
//                if(resultConsumer != null){
//                    CompareResult build = CompareResult.builder().t1Value(t1).t2Value(t2).desc(desc).build();
//                    resultConsumer.accept(build);
//                }
//                return;
//            }
            if (tempClass.isArray()) {
                Object[] t11 = (Object[]) t1;
                Object[] t22 = (Object[]) t2;

                if (doArrayCompare(desc, resultConsumer, t11, t22)) return;
                t11Set = Sets.newHashSet(t11);
                t22Set = Sets.newHashSet(t22);
            }
            if (Iterable.class.isAssignableFrom(tempClass)) {
                Iterable<?> t11 = (Iterable<?>) t1;
                Iterable<?> t22 = (Iterable<?>) t2;
                if (doIterableCompare(t11, t22, desc, resultConsumer)) return;
                t11Set = Sets.newHashSet(t11);
                t22Set = Sets.newHashSet(t22);
            }
            if (t22Set == null || t11Set == null) {
                return;
            }
            if ((t11Set.isEmpty() || t22Set.isEmpty()) && resultConsumer != null) {
                CompareResult build = CompareResult.builder().t1Value(t1).t2Value(t2).desc(desc).build();
                resultConsumer.accept(build);
                return;
            }
            Set<Object> t11 = Sets.difference(t11Set, t22Set);
            doIterableCompare(t11, null, desc, resultConsumer);
            Sets.SetView<Object> t22 = Sets.difference(t22Set, t11Set);
            doIterableCompare(null, t22, desc, resultConsumer);
        }
    }

    private static boolean doArrayCompare(String desc, Consumer<CompareResult> resultConsumer, Object[] t11, Object[] t22) {
        if (t11 == null || t22 == null) {
            if (t11 != null) {
                for (Object o : t11) {
                    compare(o, null, desc, resultConsumer);
                }
            }
            if (t22 != null) {
                for (Object o : t22) {
                    compare(null, o, desc, resultConsumer);
                }
            }
            return true;
        }
        return false;
    }

    private static boolean doIterableCompare(Iterable<?> t11, Iterable<?> t22, String desc, Consumer<CompareResult> resultConsumer) {
        if (t11 == null || t22 == null) {
            if (t11 != null) {
                for (Object o : t11) {
                    compare(o, null, desc, resultConsumer);
                }
            }
            if (t22 != null) {
                for (Object o : t22) {
                    compare(null, o, desc, resultConsumer);
                }
            }
            return true;
        }
        return false;
    }

    private static <T> boolean entityCompare(T t1, T t2, Consumer<CompareResult> resultConsumer, Class<?> tempClass) {
        if (ClassUtil.isEntity(tempClass)) {
            Field[] fields = doEntityInit(tempClass);
            doEntityCompare(fields, t1, t2, resultConsumer);
            return true;
        }
        return false;
    }

    private static <T> boolean doNullCompare(T t1, T t2, String desc, Consumer<CompareResult> resultConsumer) {
        if ((t1 == null || t2 == null) && resultConsumer != null) {
            CompareResult build = CompareResult.builder().t1Value(t1).t2Value(t2).desc(desc).build();
            resultConsumer.accept(build);
            return true;
        }
        return false;
    }

    public static <T> List<CompareResult> compare(T t1, T t2) {
        return compare(t1, t2, null);
    }

    public static <T> List<CompareResult> compare(T t1, T t2, String desc) {
        if (t1 == null || t2 == null) {
            return Collections.emptyList();
        }
        List<CompareResult> compareResults = new ArrayList<>();
        compare(t1, t2, desc, compareResults::add);
        return compareResults;
    }


    private static Field[] doEntityInit(Class<?> tempClass) {
        Field[] fields = COMPARE_FIELD_MAP.get(tempClass);
        if (fields != null) {
            return fields;
        }
        synchronized (COMPARE_FIELD_MAP) {
            fields = COMPARE_FIELD_MAP.get(tempClass);
            if (fields != null) {
                return fields;
            }
            List<Field> tempFields = new ArrayList<>(30);
            ReflectionUtils.doWithFields(tempClass, tempFields::add, f -> !Modifier.isFinal(f.getModifiers())
                    && !Modifier.isStatic(f.getModifiers())
                    && f.isAnnotationPresent(Compare.class)
                    && f.getAnnotation(Compare.class).enable());
            COMPARE_FIELD_MAP.put(tempClass, tempFields.toArray(new Field[0]));
        }
        return COMPARE_FIELD_MAP.get(tempClass);
    }

    @SneakyThrows
    private static <T> void doEntityCompare(Field[] fields, T t1, T t2, Consumer<CompareResult> resultConsumer) {
        if (fields == null) {
            return;
        }
        for (Field field : fields) {
            ReflectionUtils.makeAccessible(field);
            Compare annotation = AnnotationUtils.findAnnotation(field, Compare.class);
            //再来判断一次
            if (annotation == null || !annotation.enable()) {
                continue;
            }
            Object value1 = null;
            Object value2 = null;
            if (t1 == null || t2 == null) {
                if (resultConsumer != null) {
                    if (t1 != null) {
                        value1 = ReflectionUtils.getField(field, t1);
                    }
                    if (t2 != null) {
                        value2 = ReflectionUtils.getField(field, t2);
                    }
                    compare(value1, value2, annotation.desc(), resultConsumer);
//                    CompareResult build = CompareResult.builder().t1Value(value1).t2Value(value2).desc(annotation.desc()).build();
//                    resultConsumer.accept(build);
                }
                continue;
            } else {
                value1 = ReflectionUtils.getField(field, t1);
                value2 = ReflectionUtils.getField(field, t2);
            }
            if (Objects.deepEquals(value1, value2)) {
                continue;
            }
            Class<?> type = field.getType();

            boolean b = ClassUtil.isEntity(type) || ClassUtil.isIterable(type);
            if (b) {
                compare(value1, value2, annotation.desc(), resultConsumer);
                continue;
            }
            if (resultConsumer == null) {
                return;
            }
            IDescHandler<?> iDescHandler;
            Class<? extends IDescHandler<?>> descHandler = annotation.descValue();
            iDescHandler = descHandler.getDeclaredConstructor().newInstance();
            value1 = iDescHandler.descObj(value1);
            value2 = iDescHandler.descObj(value2);
            CompareResult build = CompareResult.builder().t1Value(value1).t2Value(value2).desc(annotation.desc()).build();
            resultConsumer.accept(build);
        }
    }
}
